##------------------------------------------------------------------------------
##
##	yasco-1.0 - Yet Another (Java)Script Obfuscator
##	Copyright (C) 2008-2011 Oliver Schieche <post@oliver-schieche.de>
##
##	Makes use of Yapp
##	Parse::Yapp - Copyright (c) 1998-2001, Francois Desarmenien, all right reserved.
##
##	$Id: MapId.pm 12 2012-10-01 13:57:30Z goresplatter1844@gmail.com $
##------------------------------------------------------------------------------
package Yasco::Filter::MapId;

use strict;
use vars qw[@ISA];

use Data::Dumper;
use Yasco::Filter::Result;
##------------------------------------------------------------------------------
BEGIN {
	require Yasco::Filter;
	@ISA = qw[Yasco::Filter];
}
##------------------------------------------------------------------------------
sub tokens
	{
		qq{DOT,ID}
	}
##------------------------------------------------------------------------------
sub new
	{
	    my $self  = shift;
	    my $options = shift;
	    $self = $self->SUPER::new($options);
		$self->{IGNORE} = {map{$_ => 1} @{ref $self->{ignore} ? $self->{ignore} : ($self->{ignore} ? [$self->{ignore}] : [])}};
		delete $self->{ignore};
		$self->{IGNORE_NEXT} = 0;
		$self->{NEXT_ID} = undef;
		$self->{RENAMED} = {};

		$self->{RANDOM_NAMES} = {} if $self->{'random-names'};

	    $self
	}
##------------------------------------------------------------------------------
sub get_token()
	{
		my $self = shift;
		my ($o,$text) = @_;

		for ($text) {
			m{^//!\[IGNORE]} and do {
				$o->verbose(0x60, "Ignoring next declaration\n");
				$self->{IGNORE_NEXT} = 1;
				return undef;
			};

			m{^//!\[NAME=(\w+)]} and do {
				$self->{NEXT_ID} = $1;
				$o->verbose(0x60, "Next identifier will be mapped to `%s`.\n", $self->{NEXT_ID});
				return undef;
			};
		}

		undef
	}
##------------------------------------------------------------------------------
sub register_node()
	{
		my $self = shift;
		my ($o,$node) = @_;

		return if 'ID' ne $$node->op;

		if ($self->{IGNORE_NEXT})
			{
				$o->verbose(0x60, "Ignoring declaration of `%s`\n", $$node->left);
				$self->{IGNORE}->{$$node->left} = 1;
				$self->{IGNORE_NEXT} = 0;
			}

		if ($self->{NEXT_ID})
			{
				$o->verbose(0x60, "\t`%s` is `%s`\n", $$node->left, $self->{NEXT_ID});
				$self->{RENAMED}->{$$node->left} = $self->{NEXT_ID};
				$$node->{MAP_AS} = $self->{NEXT_ID};
				$self->{NEXT_ID} = undef;
			}

		1
	}
##------------------------------------------------------------------------------
sub execute
	{
		my $self = shift;
		my ($o,$node) = @_;
		my $name = $node->left;

		throw Yasco::Filter::Result($name) if $name =~ m{^this|\$\w*$};

		$self->_rewrite_name($o,$node);

		MAP_NAME: {
			my $scope = \${$o->{_OB_DATA}->{scopes}}[0];
			my $peek = $o->insn_stack(1);

			do {
				local($o->{verbose_indent});
				$o->verbose(0x20, "[L%4d]", 1+$node->line);
			};

			$o->verbose(0x20, "Mapping `%s` [%s] \@scope #%d... ", $name, $peek, $o->scope_level);

			do {
				local($o->{verbose_indent});

				$o->verbose(0x20, "[scope nesting too low]\n"),
				throw Yasco::Filter::Result($name)
					if defined($o->option(qq{map_scope_start})) && $o->option(qq{map_scope_start}) > $o->scope_level;

				$o->verbose(0x20, "[mapping disabled]\n"),
				throw Yasco::Filter::Result($name)
					if $o->{no_id_mapping};

				$o->verbose(0x20, "[WERE ON THE RIGHT SIDE OF A DOT]\n"),
				throw Yasco::Filter::Result($name)
					if 'DOT_RIGHT_SIDE' eq $peek;

				$o->verbose(0x20, "[IGNORE]\n"),
				shift @{${$scope}->{ignore}}, throw Yasco::Filter::Result($name)
					if ${${$scope}->{ignore}}[0];

				$o->verbose(0x20, "[IGNORE BY DEFINITION]\n"),
				last MAP_NAME if 1 == ($self->{IGNORE}->{$name} || 0);

				$o->verbose(0x20, "[MODE=%s] ", ${$scope}->{mode});
			};

			for (${$scope}->{mode}) {
				/collect_vars/ and do
					{
						my $assign = 0;

						INSN_PEEK: for ($peek) {
							$assign = 1, last INSN_PEEK
								if /^(?:LHS|VAR|FUNC_DECL)$/;
						}

						unless($assign)
							{
								$o->verbose(0x20, "nothing to collect; finding... ");
								for (my $n = 0; $n <= $#{$o->{_OB_DATA}->{scopes}}; $n++)
									{
										$o->verbose(0x20, "[FOUND scope=%d `%s`]\n", $n, ${$o->{_OB_DATA}->{scopes}}[$n]->{ids}->{$name}),
										$name = ${$o->{_OB_DATA}->{scopes}}[$n]->{ids}->{$name}, last MAP_NAME
											if defined(${$o->{_OB_DATA}->{scopes}}[$n]->{ids}->{$name});
									}

								$o->verbose(0x20, "[NOTHING]\n");
								last MAP_NAME;
							}

						foreach my $scope_no (0..$#{$o->{_OB_DATA}->{scopes}})
							{
								FIND_FUNC: {
									last FIND_FUNC unless defined($o->{_OB_DATA}->{scopes}->[$scope_no]->{functions});
									last FIND_FUNC unless defined($o->{_OB_DATA}->{scopes}->[$scope_no]->{functions}->{$name});

									$o->verbose(0x20, "found from scope #%d's function list: `%s`\n", $scope_no, $o->{_OB_DATA}->{scopes}->[$scope_no]->{functions}->{$name});
									$name = $o->{_OB_DATA}->{scopes}->[$scope_no]->{functions}->{$name};
									last MAP_NAME;
								}

								FIND_SYM: {
									last FIND_SYM unless defined($o->{_OB_DATA}->{scopes}->[$scope_no]->{symbols});
									last FIND_SYM unless defined($o->{_OB_DATA}->{scopes}->[$scope_no]->{symbols}->{$name});

									$o->verbose(0x20, "found from scope #%d's symbols list: `%s`\n", $scope_no, $o->{_OB_DATA}->{scopes}->[$scope_no]->{symbols}->{$name});
									$name = $o->{_OB_DATA}->{scopes}->[$scope_no]->{symbols}->{$name};
									last MAP_NAME;
								}

								FIND_ID: {
									last FIND_ID unless defined($o->{_OB_DATA}->{scopes}->[$scope_no]->{ids}->{$name});

									$o->verbose(0x20, "found from scope #%d's id list: `%s`\n", $scope_no, $o->{_OB_DATA}->{scopes}->[$scope_no]->{ids}->{$name});
									$name = $o->{_OB_DATA}->{scopes}->[$scope_no]->{ids}->{$name};
									last MAP_NAME;
								}
							}

						my $newname;

						if (exists($node->{MAP_AS}))
							{
								$newname = $node->{MAP_AS};
							}
						else
							{
								$newname = $self->newname($o,$name);
							}

						${$o->{_OB_DATA}->{scopes}}[0]->{ids}->{$name} = $newname;

						$o->verbose(0x20, "[NEWNAME => %s]\n", $newname);
						$name = $newname;
						last MAP_NAME
					};

				/collect/ and do
					{
						for (my $n = 0; $n <= $#{$o->{_OB_DATA}->{scopes}}; $n++)
							{
								$o->verbose(0x20, "[FOUND scope=%d `%s`]\n", $n, ${$o->{_OB_DATA}->{scopes}}[$n]->{ids}->{$name}),
								throw Yasco::Filter::Result(${$o->{_OB_DATA}->{scopes}}[$n]->{ids}->{$name})
									if defined(${$o->{_OB_DATA}->{scopes}}[$n]->{ids}->{$name});
							}

						my $newname;

						$newname = 'p'.${$scope}->{count}++		unless ref $self->{RANDOM_NAMES};
						$newname = $self->mkrandnm($name)			if ref $self->{RANDOM_NAMES};

						${$scope}->{parameter_vars}->{$name} = $newname;
						${$scope}->{ids}->{$name} = $newname;

						$o->verbose(0x20, "[NEWNAME => %s]\n", $newname);
						$name = $newname;
						last MAP_NAME
					};

				/find/ and do
					{
						warn sprintf('A "%s" statement from a "%s" statement tried to look up undeclared identifier "%s"', $o->insn_stack(0), $peek, $name)
							if $name =~ m{^[pv]\d+$};

						for (my $n = 0; $n <= $#{$o->{_OB_DATA}->{scopes}}; $n++)
							{
								$o->verbose(0x20, "[FOUND scope=%d `%s`]\n", $n, ${$o->{_OB_DATA}->{scopes}}[$n]->{ids}->{$name}),
								throw Yasco::Filter::Result(${$o->{_OB_DATA}->{scopes}}[$n]->{ids}->{$name})
									if defined(${$o->{_OB_DATA}->{scopes}}[$n]->{ids}->{$name});
							}

						WARN_GLOBAL: {
							last WARN_GLOBAL if $name eq 'document' || $name eq 'window' || $name eq 'arguments';
							last WARN_GLOBAL if $o->option('no_warnings');
							last WARN_GLOBAL if ${$node->parent}->op =~ m{^ARRAY_ACCESS|CALL|DOT|INSTANCEOF|NEW$};

							warn sprintf('[WARNING] near line %d: %s: global variable "%s" detected'."\n", 1+$node->{line}, ${$node->parent}->op, $name);
						}
						$o->verbose(0x20, "[NOTHING]\n");
						last MAP_NAME
					};
			}

			die "Stack all messed up: " . Dumper($scope);
		}

		throw Yasco::Filter::Result($name)
	}
##------------------------------------------------------------------------------
sub _rewrite_name
	{
		my $self = shift;
		my ($o,$node) = @_;
		my ($code,$temp,$enc);

		$enc = $o->option(qq{name_encrypt});

		DOTTIZE: {
			return if 'DOT' ne $node->op;
			last DOTTIZE if !$enc;
			last DOTTIZE if 'ID' ne $node->right->op;

			$code = $node->left->reconstruct;

			ENC_LEFT:
				{
					last ENC_LEFT if (2 != $enc || 'ID' ne $node->left->op);
					last ENC_LEFT if $code =~ m{^this|\$\w+$};

					$Yasco::prefixed_codes->{'eval'} = 'e' . int(99*rand),
					push(@Yasco::prefix_code, 'var ' . $Yasco::prefixed_codes->{'eval'} . '=function(a0){return eval(a0);};')
						if !exists $Yasco::prefixed_codes->{'eval'};

					$temp = Yasco::Node->new(qq{STRING}, "'" . $code . "'");
					$code = $Yasco::prefixed_codes->{'eval'} . '(' . $temp->reconstruct . ')';
				}

			$o->push_insn('DOT_RIGHT_SIDE');
			$temp = Yasco::Node->new(qq{STRING}, "'" . $node->right->reconstruct . "'");
			$o->pop_insn;

			$code .= "[" . $temp->reconstruct . "]";
			throw Yasco::Filter::Result($code);
		}

		throw Yasco::Filter::Result(undef)
	}
##------------------------------------------------------------------------------
sub post_process
	{
	}
##------------------------------------------------------------------------------
sub newname
	{
		my $self = shift;
		my ($o,$name) = @_;

		my $newname;

		if (ref $self->{RANDOM_NAMES})
			{
				$newname = $self->mkrandnm($name);
			}
		else
			{
				$newname = 'v' . ${$o->{_OB_DATA}->{scopes}}[0]->{count}++;
			}

		$newname
	}
##------------------------------------------------------------------------------
sub mkrandnm
	{
		my $self = shift;
		my $name = shift;
		my $max = (exists($self->{'max-id-length'}) ? int($self->{'max-id-length'}) : 8);

		for (;;) {
			my $rand = ['A'..'Z','a'..'z'];
			$rand = join '', map {$rand->[int(rand()*(0+@$rand))]} 0..int($max*rand);
			next if exists($self->{RANDOM_NAMES}->{$rand});
			$self->{RANDOM_NAMES}->{$rand} = $name;
			return $rand
		}
	}
##------------------------------------------------------------------------------
1